/* src/interfaces/ecpg/ecpglib/prepare.c */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include <ctype.h>

#include "ecpgerrno.h"
#include "ecpglib.h"
#include "ecpglib_extern.h"
#include "ecpgtype.h"
#include "sqlca.h"

#define STMTID_SIZE 32

/*
 * 语句缓存包含 stmtCacheNBuckets 个哈希桶，每个桶
 * 具有 stmtCacheEntPerBucket 个条目，我们根据需要进行回收，
 * 放弃桶中执行次数最少的条目。
 * stmtCacheEntries[0] 从不使用，因此零可以作为“未找到”
 * 指示器。
 */
#define stmtCacheNBuckets		2039	/* 应该是一个质数 */
#define stmtCacheEntPerBucket	8

#define stmtCacheArraySize (stmtCacheNBuckets * stmtCacheEntPerBucket + 1)

typedef struct
{
	int			lineno;
	char		stmtID[STMTID_SIZE];
	char	   *ecpgQuery;
	long		execs;			/* 执行次数 */
	const char *connection;		/* 语句的连接 */
} stmtCacheEntry;

static int	nextStmtID = 1;
static stmtCacheEntry *stmtCacheEntries = NULL;

static bool fc_deallocate_one(int fc_lineno, enum COMPAT_MODE fc_c, struct connection *fc_con,
						   struct prepared_statement *fc_prev, struct prepared_statement *fc_this);

static bool fc_isvarchar(unsigned char fc_c)
{
	if (isalnum(fc_c))
		return true;

	if (fc_c == '_' || fc_c == '>' || fc_c == '-' || fc_c == '.')
		return true;

	if (fc_c >= 128)
		return true;

	return false;
}

bool ecpg_register_prepared_stmt(struct statement *fc_stmt)
{
	struct statement *fc_prep_stmt;
	struct prepared_statement *fc_this;
	struct connection *fc_con = fc_stmt->connection;
	struct prepared_statement *fc_prev = NULL;
	int			fc_lineno = fc_stmt->lineno;

	/* 检查我们是否已经准备了这个语句 */
	fc_this = ecpg_find_prepared_statement(fc_stmt->name, fc_con, &fc_prev);
	if (fc_this && !fc_deallocate_one(fc_lineno, ECPG_COMPAT_PGSQL, fc_con, fc_prev, fc_this))
		return false;

	/* 分配新的语句 */
	fc_this = (struct prepared_statement *) ecpg_alloc(sizeof(struct prepared_statement), fc_lineno);
	if (!fc_this)
		return false;

	fc_prep_stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), fc_lineno);
	if (!fc_prep_stmt)
	{
		ecpg_free(fc_this);
		return false;
	}
	memset(fc_prep_stmt, 0, sizeof(struct statement));

	/* 创建语句 */
	fc_prep_stmt->lineno = fc_lineno;
	fc_prep_stmt->connection = fc_con;
	fc_prep_stmt->command = ecpg_strdup(fc_stmt->command, fc_lineno);
	fc_prep_stmt->inlist = fc_prep_stmt->outlist = NULL;
	fc_this->name = ecpg_strdup(fc_stmt->name, fc_lineno);
	fc_this->stmt = fc_prep_stmt;
	fc_this->prepared = true;

	if (fc_con->prep_stmts == NULL)
		fc_this->next = NULL;
	else
		fc_this->next = fc_con->prep_stmts;

	fc_con->prep_stmts = fc_this;
	return true;
}

static bool fc_replace_variables(char **fc_text, int fc_lineno)
{
	bool		fc_string = false;
	int			fc_counter = 1,
				fc_ptr = 0;

	for (; (*fc_text)[fc_ptr] != '\0'; fc_ptr++)
	{
		if ((*fc_text)[fc_ptr] == '\'')
			fc_string = fc_string ? false : true;

		if (fc_string || (((*fc_text)[fc_ptr] != ':') && ((*fc_text)[fc_ptr] != '?')))
			continue;

		if (((*fc_text)[fc_ptr] == ':') && ((*fc_text)[fc_ptr + 1] == ':'))
			fc_ptr += 2;			/* 跳过 '::' */
		else
		{
			/* 我们所需大小的粗略估计： */
			int			fc_buffersize = sizeof(int) * CHAR_BIT * 10 / 3;
			int			fc_len;
			char	   *fc_buffer,
					   *fc_newcopy;

			if (!(fc_buffer = (char *) ecpg_alloc(fc_buffersize, fc_lineno)))
				return false;

			snprintf(fc_buffer, fc_buffersize, "$%d", fc_counter++);

			for (fc_len = 1; (*fc_text)[fc_ptr + fc_len] && fc_isvarchar((*fc_text)[fc_ptr + fc_len]); fc_len++)
				 /* 跳过 */ ;
			if (!(fc_newcopy = (char *) ecpg_alloc(strlen(*fc_text) - fc_len + strlen(fc_buffer) + 1, fc_lineno)))
			{
				ecpg_free(fc_buffer);
				return false;
			}

			memcpy(fc_newcopy, *fc_text, fc_ptr);
			strcpy(fc_newcopy + fc_ptr, fc_buffer);
			strcat(fc_newcopy, (*fc_text) +fc_ptr + fc_len);

			ecpg_free(*fc_text);
			ecpg_free(fc_buffer);

			*fc_text = fc_newcopy;

			if ((*fc_text)[fc_ptr] == '\0')	/* 我们达到了结束 */
				fc_ptr--;			/* 由于我们将在顶层
								 * 循环中 (*text)[ptr]++ */
		}
	}
	return true;
}

static bool fc_prepare_common(int fc_lineno, struct connection *fc_con, const char *fc_name, const char *fc_variable)
{
	struct statement *fc_stmt;
	struct prepared_statement *fc_this;
	PGresult   *fc_query;

	/* 分配新的语句 */
	fc_this = (struct prepared_statement *) ecpg_alloc(sizeof(struct prepared_statement), fc_lineno);
	if (!fc_this)
		return false;

	fc_stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), fc_lineno);
	if (!fc_stmt)
	{
		ecpg_free(fc_this);
		return false;
	}

	/* 创建语句 */
	fc_stmt->lineno = fc_lineno;
	fc_stmt->connection = fc_con;
	fc_stmt->command = ecpg_strdup(fc_variable, fc_lineno);
	fc_stmt->inlist = fc_stmt->outlist = NULL;

	/* 如果我们在语句中有 C 变量，将其替换为 '?' */
	fc_replace_variables(&(fc_stmt->command), fc_lineno);

	/* 将准备好的语句添加到我们的列表中 */
	fc_this->name = ecpg_strdup(fc_name, fc_lineno);
	fc_this->stmt = fc_stmt;

	/* 最后真的准备好语句 */
	fc_query = PQprepare(fc_stmt->connection->connection, fc_name, fc_stmt->command, 0, NULL);
	if (!ecpg_check_PQresult(fc_query, fc_stmt->lineno, fc_stmt->connection->connection, fc_stmt->compat))
	{
		ecpg_free(fc_stmt->command);
		ecpg_free(fc_this->name);
		ecpg_free(fc_this);
		ecpg_free(fc_stmt);
		return false;
	}

	ecpg_log("prepare_common on line %d: name %s; query: \"%s\"\n", fc_stmt->lineno, fc_name, fc_stmt->command);
	PQclear(fc_query);
	fc_this->prepared = true;

	if (fc_con->prep_stmts == NULL)
		fc_this->next = NULL;
	else
		fc_this->next = fc_con->prep_stmts;

	fc_con->prep_stmts = fc_this;
	return true;
}

/* 处理 EXEC SQL PREPARE 语句 */
/* 问号不是必须的，但出于暂时不改变 API 的考虑保留在这里 */
bool ECPGprepare(int fc_lineno, const char *fc_connection_name, const bool fc_questionmarks,
			const char *fc_name, const char *fc_variable)
{
	struct connection *fc_con;
	struct prepared_statement *fc_this,
			   *fc_prev;

	(void) fc_questionmarks;		/* 让编译器安静下来 */

	fc_con = ecpg_get_connection(fc_connection_name);
	if (!ecpg_init(fc_con, fc_connection_name, fc_lineno))
		return false;

	/* 检查我们是否已经准备了这个语句 */
	fc_this = ecpg_find_prepared_statement(fc_name, fc_con, &fc_prev);
	if (fc_this && !fc_deallocate_one(fc_lineno, ECPG_COMPAT_PGSQL, fc_con, fc_prev, fc_this))
		return false;

	return fc_prepare_common(fc_lineno, fc_con, fc_name, fc_variable);
}

struct prepared_statement *
ecpg_find_prepared_statement(const char *fc_name,
							 struct connection *fc_con, struct prepared_statement **fc_prev_)
{
	struct prepared_statement *fc_this,
			   *fc_prev;

	for (fc_this = fc_con->prep_stmts, fc_prev = NULL;
		 fc_this != NULL;
		 fc_prev = fc_this, fc_this = fc_this->next)
	{
		if (strcmp(fc_this->name, fc_name) == 0)
		{
			if (fc_prev_)
				*fc_prev_ = fc_prev;
			return fc_this;
		}
	}
	return NULL;
}

static bool fc_deallocate_one(int fc_lineno, enum COMPAT_MODE fc_c, struct connection *fc_con,
			   struct prepared_statement *fc_prev, struct prepared_statement *fc_this)
{
	bool		fc_r = false;

	ecpg_log("deallocate_one on line %d: name %s\n", fc_lineno, fc_this->name);

	/* 首先在后端解除分配语句 */
	if (fc_this->prepared)
	{
		char	   *fc_text;
		PGresult   *fc_query;

		fc_text = (char *) ecpg_alloc(strlen("deallocate \"\" ") + strlen(fc_this->name), fc_this->stmt->lineno);

		if (fc_text)
		{
			sprintf(fc_text, "deallocate \"%s\"", fc_this->name);
			fc_query = PQexec(fc_this->stmt->connection->connection, fc_text);
			ecpg_free(fc_text);
			if (ecpg_check_PQresult(fc_query, fc_lineno,
									fc_this->stmt->connection->connection,
									fc_this->stmt->compat))
			{
				PQclear(fc_query);
				fc_r = true;
			}
		}
	}

	/*
	 * 忽略所有错误，因为我们不知道可以释放的光标列表。
	 * 我们必须信任软件。
	 */
	if (!fc_r && !INFORMIX_MODE(fc_c))
	{
		ecpg_raise(fc_lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, fc_this->name);
		return false;
	}

	/* 好的，释放所有资源 */
	ecpg_free(fc_this->stmt->command);
	ecpg_free(fc_this->stmt);
	ecpg_free(fc_this->name);
	if (fc_prev != NULL)
		fc_prev->next = fc_this->next;
	else
		fc_con->prep_stmts = fc_this->next;

	ecpg_free(fc_this);
	return true;
}

/* 处理 EXEC SQL DEALLOCATE PREPARE 语句 */
bool ECPGdeallocate(int fc_lineno, int fc_c, const char *fc_connection_name, const char *fc_name)
{
	struct connection *fc_con;
	struct prepared_statement *fc_this,
			   *fc_prev;

	fc_con = ecpg_get_connection(fc_connection_name);
	if (!ecpg_init(fc_con, fc_connection_name, fc_lineno))
		return false;

	fc_this = ecpg_find_prepared_statement(fc_name, fc_con, &fc_prev);
	if (fc_this)
		return fc_deallocate_one(fc_lineno, fc_c, fc_con, fc_prev, fc_this);

	/* 找不到准备好的语句 */
	if (INFORMIX_MODE(fc_c))
		return true;
	ecpg_raise(fc_lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, fc_name);
	return false;
}

bool ecpg_deallocate_all_conn(int fc_lineno, enum COMPAT_MODE fc_c, struct connection *fc_con)
{
	/* 解除分配所有准备好的语句 */
	while (fc_con->prep_stmts)
	{
		if (!fc_deallocate_one(fc_lineno, fc_c, fc_con, NULL, fc_con->prep_stmts))
			return false;
	}

	return true;
}

bool ECPGdeallocate_all(int fc_lineno, int fc_compat, const char *fc_connection_name)
{
	return ecpg_deallocate_all_conn(fc_lineno, fc_compat,
									ecpg_get_connection(fc_connection_name));
}

char * ecpg_prepared(const char *fc_name, struct connection *fc_con)
{
	struct prepared_statement *fc_this;

	fc_this = ecpg_find_prepared_statement(fc_name, fc_con, NULL);
	return fc_this ? fc_this->stmt->command : NULL;
}

/* 返回准备好的语句 */
/* lineno 在这里未使用，但保留以不破坏 API */
char * ECPGprepared_statement(const char *fc_connection_name, const char *fc_name, int fc_lineno)
{
	(void) fc_lineno;				/* 让编译器安静 */

	return ecpg_prepared(fc_name, ecpg_get_connection(fc_connection_name));
}

/*
 * 哈希 SQL 语句 - 返回桶中第一个条目的条目编号
 */
static int fc_HashStmt(const char *fc_ecpgQuery)
{
	int			fc_stmtIx,
				fc_bucketNo,
				fc_hashLeng,
				fc_stmtLeng;
	uint64		fc_hashVal,
				fc_rotVal;

	fc_stmtLeng = strlen(fc_ecpgQuery);
	fc_hashLeng = 50;				/* 使用语句的前 50 个字符 */
	if (fc_hashLeng > fc_stmtLeng)	/* 如果语句不那么长 */
		fc_hashLeng = fc_stmtLeng;	/* 使用其实际长度 */

	fc_hashVal = 0;
	for (fc_stmtIx = 0; fc_stmtIx < fc_hashLeng; ++fc_stmtIx)
	{
		fc_hashVal = fc_hashVal + (unsigned char) fc_ecpgQuery[fc_stmtIx];
		/* 将 32 位哈希值左旋 13 位 */
		fc_hashVal = fc_hashVal << 13;
		fc_rotVal = (fc_hashVal & UINT64CONST(0x1fff00000000)) >> 32;
		fc_hashVal = (fc_hashVal & UINT64CONST(0xffffffff)) | fc_rotVal;
	}

	fc_bucketNo = fc_hashVal % stmtCacheNBuckets;

	/* 加 1，以便数组条目 0 从不使用 */
	return fc_bucketNo * stmtCacheEntPerBucket + 1;
}

/*
 * 搜索语句缓存 - 查找与 ECPG 格式查询匹配的条目
 * 如果找到，返回缓存中的条目 #
 *	 或者，如果不存在则返回零（零值条目未使用）
 */
static int fc_SearchStmtCache(const char *fc_ecpgQuery)
{
	int			fc_entNo,
				fc_entIx;

	/* 如果缓存未设置，则快速失败 */
	if (stmtCacheEntries == NULL)
		return 0;

	/* 哈希语句 */
	fc_entNo = fc_HashStmt(fc_ecpgQuery);

	/* 搜索缓存 */
	for (fc_entIx = 0; fc_entIx < stmtCacheEntPerBucket; ++fc_entIx)
	{
		if (stmtCacheEntries[fc_entNo].stmtID[0])	/* 检查条目是否在使用中 */
		{
			if (strcmp(fc_ecpgQuery, stmtCacheEntries[fc_entNo].ecpgQuery) == 0)
				break;			/* 找到了 */
		}
		++fc_entNo;				/* 增加条目编号 */
	}

	/* 如果未找到条目 - 将条目编号设置为零 */
	if (fc_entIx >= stmtCacheEntPerBucket)
		fc_entNo = 0;

	return fc_entNo;
}

/*
 * 释放语句缓存中的条目
 * 返回缓存中使用的条目 #
 *	 或者负错误代码
 */
static int fc_ecpg_freeStmtCacheEntry(int fc_lineno, int fc_compat,
						int fc_entNo)	/* 要释放的条目编号 */
{
	stmtCacheEntry *fc_entry;
	struct connection *fc_con;
	struct prepared_statement *fc_this,
			   *fc_prev;

	/* 如果缓存未设置则失败 */
	if (stmtCacheEntries == NULL)
		return -1;

	fc_entry = &stmtCacheEntries[fc_entNo];
	if (!fc_entry->stmtID[0])		/* 如果条目未在使用中则返回 */
		return 0;

	fc_con = ecpg_get_connection(fc_entry->connection);

	/* 释放 'prepared_statement' 列表条目 */
	fc_this = ecpg_find_prepared_statement(fc_entry->stmtID, fc_con, &fc_prev);
	if (fc_this && !fc_deallocate_one(fc_lineno, fc_compat, fc_con, fc_prev, fc_this))
		return -1;

	fc_entry->stmtID[0] = '\0';

	/* 释放缓存条目使用的内存 */
	if (fc_entry->ecpgQuery)
	{
		ecpg_free(fc_entry->ecpgQuery);
		fc_entry->ecpgQuery = 0;
	}

	return fc_entNo;
}

/*
 * 向语句缓存中添加条目
 * 返回使用的缓存中条目 # 或负错误代码
 */
static int fc_AddStmtToCache(int fc_lineno,		/* 语句的行号 */
			   const char *fc_stmtID,	/* 语句 ID */
			   const char *fc_connection,	/* 连接 */
			   int fc_compat,		/* 兼容性级别 */
			   const char *fc_ecpgQuery)	/* 查询 */
{
	int			fc_ix,
				fc_initEntNo,
				fc_luEntNo,
				fc_entNo;
	stmtCacheEntry *fc_entry;

	/* 如果还没有的话，分配并将缓存数组初始化为零 */
	if (stmtCacheEntries == NULL)
	{
		stmtCacheEntries = (stmtCacheEntry *)
			ecpg_alloc(sizeof(stmtCacheEntry) * stmtCacheArraySize, fc_lineno);
		if (stmtCacheEntries == NULL)
			return -1;
	}

	/* 哈希语句 */
	fc_initEntNo = fc_HashStmt(fc_ecpgQuery);

	/* 搜索未使用的条目 */
	fc_entNo = fc_initEntNo;			/* 从桶的初始条目 # 开始 */
	fc_luEntNo = fc_initEntNo;		/* 将其作为初始的“最少使用”条目 */
	for (fc_ix = 0; fc_ix < stmtCacheEntPerBucket; ++fc_ix)
	{
		fc_entry = &stmtCacheEntries[fc_entNo];
		if (!fc_entry->stmtID[0])	/* 未使用的条目 - 使用它 */
			break;
		if (fc_entry->execs < stmtCacheEntries[fc_luEntNo].execs)
			fc_luEntNo = fc_entNo;	/* 保存新的“最少使用”条目 */
		++fc_entNo;				/* 增加条目 # */
	}

	/*
	 * 如果没有找到未使用的条目，重新使用已找到的“最少使用”条目
	 * 在桶中
	 */
	if (fc_ix >= stmtCacheEntPerBucket)
		fc_entNo = fc_luEntNo;

	/* 'entNo' 是要使用的条目 - 确保它是空闲的 */
	if (fc_ecpg_freeStmtCacheEntry(fc_lineno, fc_compat, fc_entNo) < 0)
		return -1;

	/* 将查询添加到条目中 */
	fc_entry = &stmtCacheEntries[fc_entNo];
	fc_entry->lineno = fc_lineno;
	fc_entry->ecpgQuery = ecpg_strdup(fc_ecpgQuery, fc_lineno);
	fc_entry->connection = fc_connection;
	fc_entry->execs = 0;
	memcpy(fc_entry->stmtID, fc_stmtID, sizeof(fc_entry->stmtID));

	return fc_entNo;
}

/* 处理缓存和在自动准备模式下的语句准备 */
bool ecpg_auto_prepare(int fc_lineno, const char *fc_connection_name, const int fc_compat, char **fc_name, const char *fc_query)
{
	int			fc_entNo;

	/* 在语句缓存中搜索此语句 */
	fc_entNo = fc_SearchStmtCache(fc_query);

	/* 如果未找到 - 将语句添加到缓存中 */
	if (fc_entNo)
	{
		char	   *fc_stmtID;
		struct connection *fc_con;
		struct prepared_statement *fc_prep;

		ecpg_log("ecpg_auto_prepare on line %d: statement found in cache; entry %d\n", fc_lineno, fc_entNo);

		fc_stmtID = stmtCacheEntries[fc_entNo].stmtID;

		fc_con = ecpg_get_connection(fc_connection_name);
		fc_prep = ecpg_find_prepared_statement(fc_stmtID, fc_con, NULL);
		/* 此准备名称在此连接上不存在。 */
		if (!fc_prep && !fc_prepare_common(fc_lineno, fc_con, fc_stmtID, fc_query))
			return false;

		*fc_name = ecpg_strdup(fc_stmtID, fc_lineno);
	}
	else
	{
		char		fc_stmtID[STMTID_SIZE];

		ecpg_log("ecpg_auto_prepare on line %d: statement not in cache; inserting\n", fc_lineno);

		/* 生成一个语句 ID */
		sprintf(fc_stmtID, "ecpg%d", nextStmtID++);

		if (!ECPGprepare(fc_lineno, fc_connection_name, 0, fc_stmtID, fc_query))
			return false;

		fc_entNo = fc_AddStmtToCache(fc_lineno, fc_stmtID, fc_connection_name, fc_compat, fc_query);
		if (fc_entNo < 0)
			return false;

		*fc_name = ecpg_strdup(fc_stmtID, fc_lineno);
	}

	/* 增加使用计数器 */
	stmtCacheEntries[fc_entNo].execs++;

	return true;
}
